// Octopod (Controls Transfer of Pieces of Data)
// Copyright (c) 2005-2010 Barath Raghavan and James Wilcox.  All rights reserved.
//
// GPL goes here
//
// Convenient access to configuration data.

#include <string>
#include <QtCore>
#include <glog/logging.h>
#include <google/protobuf/text_format.h>

#include "config.h"

namespace octopod {

// Note that QFile expects its argument to use only '/' as a path separator.
QFile *config::OpenLocalConfig(const QString &name, QIODevice::OpenMode mode) {
  if (!config::IsClean(name)) {
    DLOG(WARNING) << Q_FUNC_INFO << "the path wasn't clean.";
    return NULL;
  }

  QString filename = QDir::homePath() + "/" + config::kConfigDir + "/" + name;

  QFile *file = new QFile(filename);
  if (!file->open(mode)) {
    DLOG(WARNING) << Q_FUNC_INFO << "couldn't open the file.";
    delete file;
    return NULL;
  }

  return file;
}

int config::ReadLocalConfig(const QString &name, QByteArray *buf,
                            QReadWriteLock *lock) {
  CHECK_NOTNULL(buf);
  CHECK_NOTNULL(lock);

  QReadLocker locker(lock);
  QScopedPointer<QFile> f(OpenLocalConfig(name, QIODevice::ReadOnly));

  if (f.isNull()) {
    DLOG(WARNING) << Q_FUNC_INFO << "couldn't open the config file.";
    return -1;
  }

  *buf = f->readAll();

  f->close();
  return 0;
}

int config::WriteLocalConfig(const QString &name, const std::string &buf,
                             QReadWriteLock *lock) {
  CHECK_NOTNULL(lock);

  QWriteLocker locker(lock);
  QScopedPointer<QFile> f(OpenLocalConfig(name, QIODevice::WriteOnly));

  if (f.isNull()) {
    DLOG(WARNING) << Q_FUNC_INFO << "couldn't open the config file.";
    return -1;
  }

  if (f->write(buf.data(), buf.size()) < 0) { // check if it wrote all the bytes?
    DLOG(WARNING) << Q_FUNC_INFO << "failed to write to the file.";
    f->close();
    return -1;
  }
  
  f->close();
  return 0;
}

int config::ParseMessageFromFile(const QString &file,
                                 google::protobuf::Message *msg,
                                 QReadWriteLock *lock) {
  CHECK_NOTNULL(msg);
  CHECK_NOTNULL(lock);

  QByteArray tmp;

  if (ReadLocalConfig(file, &tmp, lock) != 0) {
    DLOG(WARNING) << Q_FUNC_INFO << "couldn't read the local config file (might not exist)";
    return -1;
  }

  if (tmp.isEmpty()) { // nothing to get
    DLOG(INFO) << Q_FUNC_INFO << "list of shares was empty.";
    return -1;
  }

  std::string buf(tmp.data(), tmp.size());
  if (!google::protobuf::TextFormat::ParseFromString(buf, msg)) {
    DLOG(WARNING) << Q_FUNC_INFO << "couldn't parse the message";
    return -1;
  }

  return 0;

}

int config::PrintMessageToFile(const QString &file,
                               const google::protobuf::Message *msg,
                               QReadWriteLock *lock) {
  CHECK_NOTNULL(msg);
  CHECK_NOTNULL(lock);

  std::string buf;
  if (!google::protobuf::TextFormat::PrintToString(*msg, &buf)) {
    DLOG(WARNING) << Q_FUNC_INFO << "couldn't print the new share to string.";
    return -1;
  }

  return WriteLocalConfig(file, buf, lock);
}

int config::GetShareDirs(ShareList *sharedirs) {
  return ParseMessageFromFile(kSharesFile, sharedirs, &shareslock_);
}

int config::AddShareDir(const QString &path) {
  if (!IsClean(path)) {
    DLOG(WARNING) << Q_FUNC_INFO << "path wasn't clean.";
    return -1;
  }

  ShareList shares;
  if (GetShareDirs(&shares) != 0) {
    // it is not an error to have an empty list, we might be adding the first.
    DLOG(INFO) << Q_FUNC_INFO << "couldn't get the list of shares. attempting to create.";
  }

  Share *s = shares.add_share();
  s->set_path(path.toAscii().data());

  return PrintMessageToFile(kSharesFile, &shares, &shareslock_);
}

int config::GetUsers(UserList *users) {
  return ParseMessageFromFile(kKeysFile, users, &keyslock_);
}

int config::AddUser(const std::string &id, const std::string &key) {
  UserList users;
  if (GetUsers(&users) != 0) {
    // it is not an error to have an empty list, we might be adding the first.
    DLOG(INFO) << Q_FUNC_INFO << "couldn't get the list of users. attempting to create.";
  }

  User *u = users.add_user();
  u->set_id(id);
  u->set_key(key);

  return PrintMessageToFile(kKeysFile, &users, &keyslock_);
}

const User *config::FindUserByID(const std::string &id) {
  UserList users;
  if (GetUsers(&users) != 0) {
    DLOG(WARNING) << Q_FUNC_INFO << "couldn't get the list of users.";
    return NULL;
  }

  for (int i = 0; i < users.user_size(); i++) {
    const User *u = &users.user(i);
    if (u->id() == id) {
      return u;
    }
  }

  DLOG(INFO) << Q_FUNC_INFO << "couldn't find the user.";
  return NULL;
}

} // namespace octopod
